/*-------------------------------------------------------------------------
   Copyright (C) 2010 One Laptop per Child

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   In other words, you are welcome to use, share and improve this program.
   You are forbidden to forbid anyone else to use, share and improve
   what you give them.   Help stamp out software-hoarding!

   As a special exception, you may use this file as part of a free software
   library for the XO of the One Laptop per Child project without restriction.
   Specifically, if other files instantiate
   templates or use macros or inline functions from this file, or you compile
   this file and link it with other files to produce an executable, this
   file does not by itself cause the resulting executable to be covered by
   the GNU General Public License.  This exception does not however
   invalidate any other reasons why the executable file might be covered by
   the GNU General Public License.
-------------------------------------------------------------------------*/

#include <stdio.h>
#include "cc_compat.h"
#include "chip.h"
#include "uart.h"
#include "timer.h"
#include "io.h"
#include "power.h"
#include "sdi.h"
#include "queue.h"


// these act both as the conditional compilation flag, _and_
// as the runtime selector bit.  if you set any of them to 0 in
// order to get rid of the code, you may want to suppress the
// optimizer warnings you'll get from sdcc as a result with the
// pragmas below.  keil doesn't complain.
#define SDI_DEBUG_SHOW_UP       0x01
#define SDI_DEBUG_SHOW_CMD      0x02
#define SDI_DEBUG_SHOW_SWITCH   0x04
#define SDI_DEBUG_NO_TIMEOUTS   0x08
//#define SDI_DEBUG_SKIP_HOSTUP   0x10  //Unused now.
#define SDI_DEBUG_SHOW_STATES   0x20
#define SDI_DEBUG_SHOW_PACKET   0x40

// #ifdef SDCC
// #pragma disable_warning 126
// #pragma disable_warning 110
// #pragma disable_warning 84
// #endif

static unsigned char sdi_debug;
#define sdi_debug_on(x) (sdi_debug & (x))


// the SoC is supposed to finish waking (where that means internal
// clocks are stable) in 7ms.  we want to wait as short a time as
// possible, and as low as 8ms here works.  10ms is a nice number.
#define SDI_WAKE_SOC_TIMER	10


#define SDI_ACK_TIMEOUT		3000

#define SDI_STATE_DISABLED      0
#define SDI_STATE_START         1
//#define unused            2
#define SDI_STATE_UPSTREAM      3
#define SDI_STATE_WAIT          4
#define SDI_STATE_SWITCHWAIT    5
#define SDI_STATE_CMDWAIT       6
#define SDI_WAIT_HOST_READY     7
#define SDI_STATE_WAKE_SOC      8
#define SDI_STATE_UPSTREAM_SEND 9
#define SDI_HOST_NOT_READY	10

extern void sdi_do_cmd(void);

static unsigned char data ack_high_cnt;

static unsigned char	xdata sdi_state;
static unsigned int		xdata sdi_xmit_val;

#define SDI_CMD_PACKET_LENGTH 8
unsigned char xdata sdi_command_packet[SDI_CMD_PACKET_LENGTH];

static volatile unsigned char data sdi_got_ack;
static volatile unsigned char data sdi_sticky_cmd;

#if SDI_DEBUG_SHOW_STATES
static volatile unsigned char data sdi_ack_cnt;
#endif

// This buffer must be a power of 2 in size.
// The up queue is 16 bits wide so this needs
// 2x the bytes you define here
#define SDI_UP_QUEUE_DEPTH		16
static unsigned char data sdi_up_head;
static unsigned char data sdi_up_tail;				// point the current queue
unsigned int xdata sdi_up_queue[SDI_UP_QUEUE_DEPTH];

#define SDI_CMDRESP_QUEUE_DEPTH		16
static unsigned char data sdi_cmdresp_head;
static unsigned char data sdi_cmdresp_tail;				// point the current queue
unsigned char xdata sdi_cmdresp_queue[SDI_CMDRESP_QUEUE_DEPTH];
unsigned char xdata sdi_cmdresp_error;

int xdata sdi_timer;

// note:  CS is on different bits on 3930 vs 3731, and BUSY
// status is inverted on 3930 vs 3731
#define SDI_ASSERT_CS	(SHICSR |= 0x02)
#define SDI_DEASSERT_CS (SHICSR &= ~0x02)
#define SDI_BUSY		(SHICSR & 0x80)

#define HOST_IS_READY	(!(IO_PORT_READ_BIT(GPIO_EC_SPI_ACK)))
#define HOST_NOT_READY	(ack_high_cnt > 2)

void sdi_set_debug(char bits)
{
	sdi_debug = bits;
	printf("sdi_debug is now 0x%x\n", (unsigned int)sdi_debug);
}

#if SDI_DEBUG_SHOW_STATES

static unsigned char	sdi_got_ack_strobe;
static void show_state(void)
{
	static unsigned char	sdi_state_prev;
	static unsigned char	sdi_got_cmd_prev;
	static unsigned char	sdi_acklevel_prev;

	unsigned char data ts = 0;
	unsigned char data cmd_bit = IO_PORT_READ_BIT(GPIO_EC_SPI_CMD);
	unsigned char data ack_bit = IO_PORT_READ_BIT(GPIO_EC_SPI_ACK);

	if (sdi_state_prev != sdi_state)
	{
		if (!ts)
		{
			print_tstamp(); ts = 1;
		}
		sdi_state_prev = sdi_state;
		printf("SDI=%d, got_ack (%d,%02d), CMD %u ",
			   (unsigned int)sdi_state, (unsigned int)sdi_got_ack,
			   (unsigned int)sdi_ack_cnt,
			   (unsigned int)(!!cmd_bit));
	}
	if (sdi_acklevel_prev != ack_bit)
	{
		if (!ts)
		{
			print_tstamp(); ts = 1;
		}
		sdi_acklevel_prev = ack_bit;
		printf("%s ", sdi_acklevel_prev ? "AckHi" : "acklo");
	}
	if (sdi_got_ack_strobe && sdi_got_ack)
	{
		if (!ts)
		{
			print_tstamp(); ts = 1;
		}
		sdi_got_ack_strobe = 0;
		printf("%s ", "Ack" );
	}
	if (sdi_got_cmd_prev != cmd_bit)
	{
		if (!ts)
		{
			print_tstamp(); ts = 1;
		}
		sdi_got_cmd_prev = cmd_bit;
		printf("%s ", sdi_got_cmd_prev ? "Cmd" : "nocmd");
	}

	if (ts)
	{
		puts("");
	}
}
#else
#define show_state()
#endif

void intsrv_sdi(void) interrupt 27
{
	if (IO_WAKEUP_PENDING(GPIO_EC_SPI_ACK))
	{
		sdi_got_ack = 1;
		IO_WAKEUP_PENDING_CLEAR(GPIO_EC_SPI_ACK);
#if SDI_DEBUG_SHOW_STATES
		sdi_ack_cnt++;
#endif
	}
#ifdef TEST_CODE_FOR_WAKEUPS
	if (IO_WAKEUP_PENDING(GPIO_PWR_BTN))
	{
		extern bit stop_mode_active;
		if (!stop_mode_active)
		{
			IO_WAKEUP_PENDING_CLEAR(GPIO_PWR_BTN);
			puts("NO");
		}
		else
		{
			puts("STOP");
		}
		puts("power interrupt");
		printf("0x%x", (uint)E51_STA);
	}
#endif
}

void sdi_int_enable(void)
{
	P3IE3 = 1;
}

void sdi_int_disable(void)
{
	P3IE3 = 0;
	IO_WAKEUP_PENDING_CLEAR(GPIO_EC_SPI_ACK);
}

void sdi_io_init(void)
{
	// CMD - level sensitive, polled
	IO_INPUT_ONLY(GPIO_EC_SPI_CMD);

	// ACK - active low, falling edge interrupt
	IO_PULLUP_ENABLE(GPIO_EC_SPI_ACK);
	IO_INPUT_ONLY(GPIO_EC_SPI_ACK);
	IO_WAKEUP_ENABLE(GPIO_EC_SPI_ACK);
	IO_WAKEUP_POLARITY_LOW(GPIO_EC_SPI_ACK);
	IO_WAKEUP_EDGE(GPIO_EC_SPI_ACK);

#ifdef NOT_NEEDED
	// the 3731 defaults to SPI mode on these pins.
	IO_ALT_OUT_ENABLE(GPIO_10);
	IO_ALT_OUT_ENABLE(GPIO_11);
	IO_ALT_OUT_ENABLE(GPIO_12);
	IO_INPUT_ENABLE(GPIO_12);   // SPI_DI
	IO_ALT_OUT_ENABLE(GPIO_13);
#endif
}

void sdi_io_deinit(void)
{
	IO_INPUT_DISABLE(GPIO_EC_SPI_CMD);

	IO_PULLUP_DISABLE(GPIO_EC_SPI_ACK);
	IO_INPUT_DISABLE(GPIO_EC_SPI_ACK);
}

void sdi_module_init(void)
{
#if 0
	sdi_state 			= 0;
	sdi_up_head 		= 0;
	sdi_up_tail 		= 0;
	sdi_got_ack			= 0;
	sdi_timer 			= 0;
#endif

#if SDI_DEBUG_SHOW_STATES
	sdi_ack_cnt		= 0;
#endif

//	sdi_debug	= 0x20;
	if (sdi_debug_on(SDI_DEBUG_SHOW_STATES)) show_state();
}

// Divisors 0, 1, 2, 3 give 8, 4, 2, 1 MHz SPI clocks
#define SDI_CLK_DIVISOR 0

// 01: Mode 1
// CLK defaults (at idle) to 0,
// Drive data at rising edge and latch data at falling edge
#define SDI_MODE 1

void sdi_init(void)
{
	/* Setup cmd/ack pins */
	sdi_io_init();

	// Enable the SDI (master) - empirically, the divisor must be set
	// before or at the same time as the enable bit.
	SHICSR = (SDI_CLK_DIVISOR << 2) | (SDI_MODE << 4);
	SHICSR |= 0x01;   // + enable

	sdi_int_disable();

	if (sdi_debug_on(SDI_DEBUG_SHOW_STATES)) show_state();
}

void sdi_deinit(void)
{
	// disable the controller
	SHICSR &= ~0x01;

	// turn off the ACK and CMD inputs
	sdi_io_deinit();

}

// Each upstream packet is 2 bytes
void sdi_send_upstream_packet(unsigned int packet)
{
	unsigned int indat;

	/* Sending a packet "consumes" the ACK */
	sdi_got_ack=0;
#if SDI_DEBUG_SHOW_STATES
	sdi_got_ack_strobe = 1;
#endif
	SDI_ASSERT_CS;

	SHITBUF	= packet;
	while (SDI_BUSY);
	indat = SHIRBUF;
	indat <<= 8;

	SHITBUF	= (packet >> 8);
	while (SDI_BUSY);
	indat |= SHIRBUF;

	SDI_DEASSERT_CS;

	if (sdi_debug_on(SDI_DEBUG_SHOW_PACKET))
		printf("sent %04x got %04x\n", (uint)packet,indat);

}

unsigned char sdi_read_upstream_command(void)
{
	unsigned char cnt;

	/* Reading the packet "consumes" the ACK */
	sdi_got_ack=0;

	/* The state of the CMD line at this point controls sticky mode */
	sdi_sticky_cmd = IO_PORT_READ_BIT(GPIO_EC_SPI_CMD);

	SDI_ASSERT_CS;
	for (cnt=0; cnt<SDI_CMD_PACKET_LENGTH; cnt++)
	{
		// the linux driver wants the upward (i.e., junk) data to
		// have a zero channel number.  so send all zeros.
		SHITBUF	= 0;
		while (SDI_BUSY);

		sdi_command_packet[cnt] = SHIRBUF;
		if (sdi_debug_on(SDI_DEBUG_SHOW_PACKET))
			printf("%x ",(uint)sdi_command_packet[cnt]);
	}
	SDI_DEASSERT_CS;
	if (sdi_debug_on(SDI_DEBUG_SHOW_PACKET))
		puts("received");
	return 0;
}

int sdi_up_queue_add(unsigned char dest, unsigned char val)
{
	data unsigned int qval;

	if (QUEUE_SIZE(sdi_up_head,sdi_up_tail) >= (SDI_UP_QUEUE_DEPTH) )
	{
		printf("SDI up, dropping %x\n",(uint)val);
		return QUEUE_FULL;
	}
	qval = (((unsigned int)val)<<8)|dest;
	sdi_up_queue[sdi_up_head & (SDI_UP_QUEUE_DEPTH-1)] = qval;
	sdi_up_head++;
	return 0;
}

int sdi_up_queue_get(void)
{
	int retval;

	if (QUEUE_SIZE(sdi_up_head,sdi_up_tail) == 0 )
	{
		return QUEUE_EMPTY;
	}
	retval = sdi_up_queue[sdi_up_tail & (SDI_UP_QUEUE_DEPTH-1)];
	sdi_up_tail++;
	return retval;
}

#ifdef UNUSED
unsigned int sdi_up_queue_size(void)
{
#if 0
	if (KEY_QUEUE_SIZE)
	{
		print_tstamp();
		printf("<H:%d,T:%d>",(unsigned int)KeyHead,(unsigned int)KeyTail);
	}
#endif
	return QUEUE_SIZE(sdi_up_head,sdi_up_tail);
}
#endif

#ifndef NO_PS2
unsigned char sdi_up_queue_ready(void)
{
	return !(QUEUE_SIZE(sdi_up_head,sdi_up_tail) >= (SDI_UP_QUEUE_DEPTH));
}
#endif

void sdi_up_queue_clear(void)
{
	sdi_up_tail = sdi_up_head;
}

/*
 * command responses are put on this queue, so as not to
 * compete with traffic from other sources.
 */
int sdi_cmdresp_queue_add(unsigned char val)
{
	if (QUEUE_SIZE(sdi_cmdresp_head,sdi_cmdresp_tail) >= SDI_CMDRESP_QUEUE_DEPTH )
	{
		printf("SDI cmdresp, dropping %x\n", (unsigned int)val);
		return QUEUE_FULL;
	}
	sdi_cmdresp_queue[sdi_cmdresp_head & (SDI_CMDRESP_QUEUE_DEPTH-1)] = val;
	sdi_cmdresp_head++;
	return 0;
}

int sdi_cmdresp_queue_get(void)
{
	int retval;

	if (QUEUE_SIZE(sdi_cmdresp_head,sdi_cmdresp_tail) == 0 )
	{
		return QUEUE_EMPTY;
	}
	retval = sdi_cmdresp_queue[sdi_cmdresp_tail & (SDI_CMDRESP_QUEUE_DEPTH-1)];
	sdi_cmdresp_tail++;
	return retval;
}

void sdi_cmdresp_queue_clear(void)
{
	sdi_cmdresp_tail = sdi_cmdresp_head;
}

void sdi_start(void)
{
	sdi_state = SDI_STATE_START;
}

char code sdi_channel_ident[] = { ' ', 's', 'c', 'k', 't', 'e', 'd', 'E' };

void sdi_process(void)
{

	if (IO_PORT_READ_BIT(GPIO_EC_SPI_ACK))
	{
		// Don't let it rollover when in host not ready
		// for a long time.
		if (ack_high_cnt < 5) ack_high_cnt++;
	}
	else
	{
		ack_high_cnt = 0;
	}

	if (sdi_debug_on(SDI_DEBUG_SHOW_STATES)) show_state();

	switch (sdi_state)
	{
		case SDI_STATE_DISABLED:
			break;

		case SDI_STATE_START:     // 1
			sdi_int_enable();
			sdi_up_queue_clear();
			sdi_cmdresp_queue_clear();

			sdi_state = SDI_WAIT_HOST_READY;
			break;

		case SDI_HOST_NOT_READY:
			print_tstamp();
			puts("SDI: Host not ready");
			sdi_state = SDI_WAIT_HOST_READY;
			break;

		case SDI_WAIT_HOST_READY: // 7
			if (HOST_IS_READY)
			{
				print_tstamp();
				puts("SDI: Host ready");
				// We discard the events, so when the host
				// becomes ready to receive events, it won't have to deal with
				// a backlog of stuff than may no longer be timely.
				sdi_up_queue_clear();
				sdi_cmdresp_queue_clear();
				sdi_state = SDI_STATE_UPSTREAM;
			}
			break;

		case SDI_STATE_UPSTREAM:  // 3

			if (sdi_cmdresp_error)
			{
				sdi_xmit_val = (sdi_cmdresp_error << 8) | SDI_CHANNEL_CMDERROR;
				sdi_cmdresp_error = 0;
			}
			else if ((sdi_xmit_val = sdi_cmdresp_queue_get()) != QUEUE_EMPTY)
			{
				sdi_xmit_val = (sdi_xmit_val << 8) | SDI_CHANNEL_CMDRESP;
			}
			else
			{
				sdi_xmit_val = sdi_up_queue_get();
			}

			// An ACK stobe from linux is approx 1us wide.  Reading the signal
			// multiple times makes it so that a spurious ack will not
			// trigger this state.
			if (HOST_NOT_READY)
			{
				// This accomodates the case where the host is not ready to receive
				// upstream events, for example when switching from CForth to OFW,
				// when switching from OFW to Linux, or when the keyboard data is
				// being handled by the Security process and CForth or OFW has no
				// use for other types of events.
				sdi_state = SDI_HOST_NOT_READY;
			}
			else if (IO_PORT_READ_BIT(GPIO_EC_SPI_CMD))
			{
				sdi_xmit_val = 0x0000 | (SDI_CHANNEL_SWITCH);
				if (sdi_debug_on(SDI_DEBUG_SHOW_SWITCH))
				{
					print_tstamp();
					puts("CMD Switch");
				}
				sdi_send_upstream_packet(sdi_xmit_val);
				sdi_timer = get_ms_timer();
				sdi_state = SDI_STATE_SWITCHWAIT;
			}
			else if (sdi_xmit_val != QUEUE_EMPTY)
			{
				if (host_suspending() || host_in_suspend())
				{
					sdi_timer = get_ms_timer();
					sdi_state = SDI_STATE_WAKE_SOC;
					print_tstamp();
					puts("SDI: waking");
					enable_EC_IRQ();
				}
				else
				{
					sdi_state = SDI_STATE_UPSTREAM_SEND;
				}
			}
			break;

		case SDI_STATE_WAKE_SOC:	// 8
			if (check_timer(sdi_timer, SDI_WAKE_SOC_TIMER))
			{
				disable_EC_IRQ();
				sdi_state = SDI_STATE_UPSTREAM_SEND;
			}
			break;

		case SDI_STATE_UPSTREAM_SEND:	// 9
			if (sdi_debug_on(SDI_DEBUG_SHOW_UP))
			{
				unsigned char dest, dval;

				print_tstamp();
				dest = sdi_xmit_val;
				dval = sdi_xmit_val>>8;
				printf("%c^ %02x\n", sdi_channel_ident[dest], (unsigned int)dval);

			}

			sdi_send_upstream_packet(sdi_xmit_val);
			sdi_timer = get_ms_timer();
			sdi_state = SDI_STATE_WAIT;
			break;

		case SDI_STATE_WAIT:   // 4

			if (HOST_NOT_READY)
			{
				sdi_state = SDI_HOST_NOT_READY;
				break;
			}

			if (sdi_got_ack)
			{
				sdi_state = SDI_STATE_UPSTREAM;
				break;
			}

			if (!sdi_debug_on(SDI_DEBUG_NO_TIMEOUTS) &&
					check_timer(sdi_timer,SDI_ACK_TIMEOUT))
			{
				print_tstamp();
				puts("SDI wait timeout");
				sdi_state = SDI_STATE_UPSTREAM;
			}
			break;

		case SDI_STATE_SWITCHWAIT:  // 5

			if (HOST_NOT_READY)
			{
				sdi_state = SDI_HOST_NOT_READY;
				break;
			}


			// Host is ready with the command
			if (sdi_got_ack)
			{
				sdi_read_upstream_command();
				sdi_state = SDI_STATE_CMDWAIT;
				if (sdi_debug_on(SDI_DEBUG_SHOW_CMD))
				{
					print_tstamp();
					printf(" cmd: %02x args: %02x\n",
						   (unsigned int)sdi_command_packet[0],
						   (unsigned int)sdi_command_packet[1]);
				}

				sdi_do_cmd();
				break;
			}

			if (!sdi_debug_on(SDI_DEBUG_NO_TIMEOUTS) &&
					check_timer(sdi_timer,SDI_ACK_TIMEOUT))
			{
				print_tstamp();
				puts("SDI switchwait noack");
				sdi_state = SDI_STATE_UPSTREAM;
			}
			break;

		case SDI_STATE_CMDWAIT:  // 6

			if (HOST_NOT_READY)
			{
				sdi_state = SDI_HOST_NOT_READY;
				break;
			}

			// Host is ready with the command
			if (sdi_got_ack)
			{
				sdi_state = sdi_sticky_cmd
							? SDI_STATE_SWITCHWAIT
							: SDI_STATE_UPSTREAM;
				break;
			}
			if (!sdi_debug_on(SDI_DEBUG_NO_TIMEOUTS) &&
					check_timer(sdi_timer,SDI_ACK_TIMEOUT))
			{
				print_tstamp();
				puts("SDI cmdwait noack");
				sdi_state = SDI_STATE_UPSTREAM;
			}
			break;
	}
}
